﻿
using EmperialApps.WeatherSpark.Data;
using ImageTools;
using ImageTools.IO.Png;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace EmperialApps.WeatherSpark.Agent {

    /// <summary>Base call for Windows Phone live tile images.</summary>
    public abstract partial class TileImageBase : UserControl {

        /// <summary>Gets or sets a value used to modify the path where the image is saved.</summary>
        public string PathModifier { get; set; }


        /// <summary>Saves a PNG image of the tile to the specified stream.</summary>
        public void SaveImage( Stream stream ) {
            // Render tile to bitmap.
            this.InitializeLayout( );
            var bitmap = new WriteableBitmap( this, null );

            // Encode bitmap as PNG image.
            var encoder = new PngEncoder( );
            encoder.Encode( bitmap.ToImage( ), stream );
            stream.Flush( );
        }

        /// <summary>Saves a PNG image of the tile to the specified stream.</summary>
        public static void SaveImage( Stream stream, TileImageBase tileImage ) {
            tileImage.SaveImage( stream );
        }

        /// <summary>Displays the specified values on the tile.</summary>
        public abstract void DisplayValues( Forecast forecast, Units units, ForecastDisplayMode mode );


        /// <summary>Gets the child elements in the forecast panel.</summary>
        protected abstract UIElementCollection ForecastPanelChildren { get; }

        /// <summary>Gets a value indicating whether elements should be shown or hidden when a background image source is provided.</summary>
        protected virtual bool ShowAllElementsWhenUsingImage { get { return false; } }

        /// <summary>Creates a <see cref="PointCollection"/> from a collection of points.</summary>
        protected static PointCollection ToPointCollection( IEnumerable<Point> points ) {
            var collection = new PointCollection( );
            foreach( Point point in points )
                collection.Add( point );

            return collection;
        }

        /// <summary>Creates a figure from the specified set of points.</summary>
        protected static PathFigure CreatePathFigure( Point start, params Point[] points ) {
            var segment = new PolyLineSegment { Points = ToPointCollection( points ) };
            var figure = new PathFigure { IsClosed = true, IsFilled = true, StartPoint = start };
            figure.Segments.Add( segment );
            return figure;
        }

        /// <summary>Initializes a line from the specified values, and updates the position of the current time marker.</summary>
        protected static Scale DisplayTrace( Polyline trace, Shape currentTimeMarker, DataValues values, Scale valueScale, Scale timeScale, double beginOffset, double hourOffset, double verticalOffset = 0, int hours = ConvertValue.HoursPerDay ) {
            double currentValue = values.GetInterpolatedValue( hourOffset, ConvertValue.Identity );

            int extraSamples = Math.Max( 0, (int)Math.Ceiling( beginOffset / values.Interval ) );
            int beginIndex = extraSamples - Math.Sign( extraSamples );
            int beginCount = hours / values.Interval + 3;
            int beginPadding = beginIndex * values.Interval - (int)beginOffset;
            var displayValues = values.Skip( beginIndex ).Take( beginCount );

            // If scale was created with only one data point, examine display values to determine full range.
            if( valueScale.Minimum == valueScale.Maximum )
                valueScale = AkimaInterpolation.GetInterpolatedScale( valueScale.IsInverted, valueScale.ScreenSize, displayValues.Concat( new[] { valueScale.Minimum } ), values.Interval, 0.5, valueScale.Floor );

            var interpolation = new AkimaInterpolation( displayValues.Select( valueScale.ToScreen ), values.Interval );
            var points = interpolation.Interpolate( timeScale, beginPadding ).Select( p => new Point( p.Item1, verticalOffset + p.Item2 ) );
            trace.Points = ToPointCollection( points );

            var currentTimeTransform = (TranslateTransform)currentTimeMarker.RenderTransform;
            currentTimeTransform.X = timeScale.ToScreen( hourOffset - beginOffset );
            currentTimeTransform.Y =
                points.SkipWhile( p => p.X < currentTimeTransform.X )
                      .Select( p => (double?)p.Y )
                      .FirstOrDefault( ) - verticalOffset
                ?? valueScale.ToScreen( currentValue );
            return valueScale;
        }

        /// <summary>Initializes a line to display extreme high or low transition temperature in the specified scale.</summary>
        protected static void DisplayExtremeLine( Line extremeLine, double width, Scale temperatureScale, Units units ) {
            var extremes =
                units == Data.Units.Metric
                    ? new[] { 0.0, 40.0 }
                    : new[] { 32.0, 100.0 };
            foreach( double extreme in extremes ) {
                if( extreme <= temperatureScale.Minimum
                 || extreme >= temperatureScale.Maximum )
                    continue;

                double position = temperatureScale.ToScreen( extreme );
                extremeLine.Y1 = extremeLine.Y2 = position;
                extremeLine.X2 = width;
            }
        }

        /// <summary>Performs a layout pass to initialize all image elements.</summary>
        protected void InitializeLayout( ) {
            this.UpdateLayout( );

            int width = (int)this.Width;
            int height = (int)this.Height;
            this.Measure( new Size( width, height ) );
            this.Arrange( new Rect( 0, 0, width, height ) );
        }


        #region Private Members

        private static void OnDisplayValueChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
            var self = (TileImageBase)d;

            Forecast forecast = self.Forecast;
            Units? units = self.Units;
            ForecastDisplayMode? mode = self.DisplayMode;
            if( forecast != null && units != null && mode != null )
                self.DisplayValues( forecast, units.Value, mode.Value );
        }

        private void OnImageSourceChanged( ) {
            if( this.ShowAllElementsWhenUsingImage )
                return;

            Uri imageSource = this.ImageSource;
            Visibility forecastVisibility =
                imageSource == null
                    ? Visibility.Visible
                    : Visibility.Collapsed;

            foreach( UIElement child in this.ForecastPanelChildren ) {
                string name = (string)child.GetValue( FrameworkElement.NameProperty );
                if( name != "TileTitle" )
                    child.Visibility = forecastVisibility;
            }
        }

        private void OnBackgroundColorChanged( ) {
            var forecastPanel = (Panel)this.ForecastPanelChildren.Select( VisualTreeHelper.GetParent ).First( );
            forecastPanel.Background = new SolidColorBrush( this.BackgroundColor );
        }

        #endregion

    }

}
